#include "utils.h"
#include "material.h"
#include "argparser.h"
#include "sphere.h"
#include "vertex.h"
#include "mesh.h"
#include "ray.h"
#include "hit.h"

// ====================================================================
// ====================================================================

bool Sphere::intersect(const Ray &r, Hit &h, double time) const
{
	// Get the rays origin and direction
	Vec3f temporalCenter = center + velocity * time;

	Vec3f rOrigin = r.getOrigin() - temporalCenter;
	Vec3f rDir = r.getDirection();

    // Compute A, B and C coefficients
	float a = rDir.Dot3(rDir);
	float b = 2 * rDir.Dot3(rOrigin);
	float c = rOrigin.Dot3(rOrigin) - (radius * radius);

    // Find discriminant
    float disc = b * b - 4 * a * c;
    
	// Ray misses sphere
    if (disc < 0)
	{
        return false;
	}

    // compute t0 and t1
    float t0 = (-b - sqrtf(disc)) / (2.0 * a);
    float t1 = (-b + sqrtf(disc)) / (2.0 * a);

    // make sure t0 is smaller than t1
    if (t0 > t1)
    {
        // Not bigger, swap values
		std::swap(t0, t1);
    }

	// Ray is going the wrong direction
    if (t1 < 0)
	{
        return false;
	}

	// Decide the t value
	float t = (t0 < 0) ? t = t1 : t = t0;

	if (t < h.getT())
	{
		// Compute the normal
		Vec3f normal = (r.getOrigin() + t * rDir) - temporalCenter;

		normal.Normalize();

		// Set the hit
		h.set(t, material, normal);
		return true;
	}

	return false;
} 

// ====================================================================
// ====================================================================

// helper function to place a grid of points on the sphere
Vec3f ComputeSpherePoint(double s, double t, const Vec3f center, double radius)
{
  double angle = 2*M_PI*s;
  double y = -cos(M_PI*t);
  double factor = sqrt(1-y*y);
  double x = factor*cos(angle);
  double z = factor*-sin(angle);
  Vec3f answer = Vec3f(x,y,z);
  answer *= radius;
  answer += center;
  return answer;
}

// for OpenGL rendering, and for radiosity, we need a patch based
// version of the sphere
void Sphere::addRasterizedFaces(Mesh *m, ArgParser *args) {
  
  int h = args->sphere_horiz;
  int v = args->sphere_vert;
  assert (h % 2 == 0);
  int i,j;
  int va,vb,vc,vd;
  Vertex *a,*b,*c,*d;
  int offset = m->numVertices();

  // place vertices
  m->addVertex(center+radius*Vec3f(0,-1,0));  // bottom vertex
  for (j = 1; j < v; j++) {  // middle vertices
    for (i = 0; i < h; i++) {
      double s = i / double(h);
      double t = j / double(v);
      m->addVertex(ComputeSpherePoint(s,t,center,radius));
    }
  }
  m->addVertex(center+radius*Vec3f(0,1,0));  // top vertex

  // create the middle patches
  for (j = 1; j < v-1; j++) {
    for (i = 0; i < h; i++) {
      va = 1 +  i      + h*(j-1);
      vb = 1 + (i+1)%h + h*(j-1);
      vc = 1 +  i      + h*(j);
      vd = 1 + (i+1)%h + h*(j);
      a = m->getVertex(offset + va);
      b = m->getVertex(offset + vb);
      c = m->getVertex(offset + vc);
      d = m->getVertex(offset + vd);
      m->addRasterizedPrimitiveFace(a,b,d,c,material);
    }
  }

  for (i = 0; i < h; i+=2) {
    // create the bottom patches
    va = 0;
    vb = 1 +  i;
    vc = 1 + (i+1)%h;
    vd = 1 + (i+2)%h;
    a = m->getVertex(offset + va);
    b = m->getVertex(offset + vb);
    c = m->getVertex(offset + vc);
    d = m->getVertex(offset + vd);
    m->addRasterizedPrimitiveFace(d,c,b,a,material);
    // create the top patches
    va = 1 + h*(v-1);
    vb = 1 +  i      + h*(v-2);
    vc = 1 + (i+1)%h + h*(v-2);
    vd = 1 + (i+2)%h + h*(v-2);
    a = m->getVertex(offset + va);
    b = m->getVertex(offset + vb);
    c = m->getVertex(offset + vc);
    d = m->getVertex(offset + vd);
    m->addRasterizedPrimitiveFace(b,c,d,a,material);
  }
}
